S03-01 JS-基础-基础语法
[TOC]
概述
前端三大核心
前端开发最主要需要掌握的是三个知识点:HTML、CSS、JavaScript

计算机语言
计算机语言(Computer Language):指用于人与计算机之间通讯的语言,是人与计算机之间传递信息的介质。其概念比通用的编程语言要更广泛。例如,HTML是标记语言,也是计算机语言,但并不是编程语言。
前面我们已经学习了 HTML 和 CSS 很多相关的知识:
- HTML:是一种标记语言
- CSS:是一种样式语言;
他们本身都是属于计算机语言, 因为都在和计算机沟通交流;
- 在生活中两个人想要沟通, 必然是通过某一种语言(中文/英语/粤语/东北话)
- 计算机语言就是我们人和计算机进行交流要学习的语言;
网页的三大组成部分的另外一个核心就是 JavaScript:JavaScript 必然也是一种计算机语言;

编程语言
概述
编程语言(Programming Language):用来定义计算机程序的形式语言。它是一种被标准化的交流技巧, 用来向计算机发出指令,一种能够让程序员准确地定义计算机所需要使用数据的计算机语言,并精确地定义在不同情况下应当采取的行动。
事实上, JavaScript 我们可以对其有更加精准的说法:一种编程语言。
编程语言的特点:
- 数据和数据结构
- 指令及流程控制
- 引用机制和重用机制
- 设计哲学
这样的区分是否有意义呢?我们这里不讨论,我这里只把最专业的定义来告诉大家。
常见编程语言


编程语言历史
阶段一: 机器语言:
- 计算机的存储单元只有 0 和 1 两种状态,因此一串代码要让计算机“读懂”,这串代码只能由数字 0 和 1 组成。
- 像这种由数字 0 和 1 按照一定的规律组成的代码就叫机器码,也叫二进制编码。
- 一定长度的机器码组成了机器指令,用这些机器指令所编写的程序就称为机器语言。

优点:
- 无需编译解析:代码能被计算机直接识别,不需要经过编译解析;
- 执行效率高:直接对硬件产生作用,程序的执行效率非常高;
缺点:
- 可读性差:程序全是些 0 和 1 的指令代码,可读性差,还容易出错;
- 不易编写:目前没有人这样开发;
阶段二: 汇编语言:
- 为了解决机器语言的缺陷,人们发明了另外一种语言——汇编语言。
- 这种语言用符号来代替冗长的、难以记忆的 0、1 代码。(mov/push 指令,经过汇编器,汇编代码再进一步转成 0101)

优点:
- 像机器语言一样,可以直接访问、控制计算机的各种硬件设备;
- 占用内存少,执行速度快;
缺点:
- 不兼容:不同的机器有不同的汇编语言语法和编译器,代码缺乏可移植性。一个程序只能在一种机器上运行,换到其他机器上可能就不能运行;
- 不易编写/调试:符号非常多、难记。即使是完成简单的功能也需要大量的汇编语言代码,很容易产生 BUG,难于调试;
应用场景:操作系统内核、驱动程序、单片机程序;
阶段三: 高级语言:
- 最好的编程语言应该是什么? 自然语言;
- 而高级语言, 就是接近自然语言, 更符合人类的思维方式
- 跟和人交流的方式很相似, 但是大多数编程语言都是国外发明的, 因为都是接近于英文的交流方式


优点:
- 易于理解:简单、易用、易于理解,语法和结构类似于普通英文;
- 易于编写:远离对硬件的直接操作,使得一般人经过学习之后都可以编程,而不用熟悉硬件知识;
- 兼容性好:一个程序还可以在不同的机器上运行,具有可移植性;
缺点:
- 需要编译:程序不能直接被计算机识别,需要经编译器翻译成二进制指令后,才能运行到计算机上;
- 种类繁多:JavaScript 、 C 语言、C++、C#、Java、Objective-C 、Python 等;
机器语言和高级语言
在前端,我们需要学好的只有一门高级语言:JavaScript。

JavaScript
概述
JavaScript(JS):是一种高级的、解释型的编程语言。它是一门基于原型、头等函数的语言,是一门多范式的语言,它支持面向对象程序设计,指令式编程,以及函数式编程。
从上面的定义中, 我们会发现很多关键词:
- 解释型语言? 原型? 头等函数? 多范式? 面向对象程序设计? 指令式编程? 函数式编程?
- 这些改变往往会让人不知所云,需要我们完全掌握 JavaScript 再来回头看,每一个词语描述的都非常准确;
现在只需要知道,通俗的说法:
- JavaScript 是一门高级编程语言, 是前端开发的重要组成部分!
HTML 和 CSS 也是前端开发的重要组成部分, 而 JavaScript 是前端开发的灵魂;
JS 历史
Javascript 诞生:1994 年 Netscape 公司出于实现与访问者互动的目的创造了 Javascript。
1994 年,网景公司(Netscape)发布了 Navigator 浏览器 0.9 版。
- 这是历史上第一个比较成熟的网络浏览器,轰动一时。
- 但是,这个版本的浏览器只能用来浏览,不具备与访问者互动的能力。
- 网景公司急需一种网页脚本语言,使得浏览器可以与网页互动。


网景公司当时想要选择一种语言来嵌入到浏览器中:
- 采用现有的语言,比如 Perl、Python、Tcl、Scheme 等等, 允许它们直接嵌入网页;
- 1995 年网景公司招募了程序员Brendan Eich,希望将 Scheme 语言作为网页脚本语言的可能性;
就在这时,发生了另外一件大事:1995 年 Sun 公司将 Oak 语言改名为 Java,正式向市场推出;
- Java 推出之后立马在市场上引起了轰动,Java 当初有一个口号:“write once run anywhere”;
- 网景公司动了心,决定与 Sun 公司结成联盟,希望将 Java 嵌入到网页中来运行;
- Brendan Eich 本人非常热衷于 Scheme,但是管理层那个时候有点倾向于 Java,希望可以简化 Java 来适应网页脚本的需求;
但是 Brendan Eich 对此并不感兴趣,他用 10 天时间设计出来了 JavaScript;
- 最初这门语言的名字是 Mocha(摩卡);
- 在 Navigator2.0 beta 版本更名为 LiveScript;
- 在 Navigator2.0 beta 3 版本正式重命名为 JavaScript,当时是为了给这门语言搭上 Java 这个热词;
当然 10 天设计出来语言足够说明 Brendan Eich 是天才,但是这门语言当时更像是一个多种语言的大杂烩;
- 借鉴 C 语言的基本语法;
- 借鉴 Java 语言的数据类型和内存管理;
- 借鉴 Scheme 语言,将函数提升到"第一等公民"(first class)的地位;
- 借鉴 Self 语言,使用基于原型(prototype)的继承机制。
Brendan Eich 曾经这样描述过 JavaScript:
- 与其说我爱 Javascript,不如说我恨它,它是 C 语言和 Self 语言一夜情的产物;
- 十八世纪英国文学家约翰逊博士说得好:'它的优秀之处并非原创,它的原创之处并不优秀。’
- (the part that is good is not original, and the part that is original is not good.)
微软的竞品 JScript:
微软公司于 1995 年首次推出 Internet Explorer,从而引发了与 Netscape 的浏览器大战。
- 微软对 Netscape Navigator 解释器进行了逆向工程,创建了 JScript,以与处于市场领导地位的网景产品同台竞争;
- 这个时候对于开发者来说是一场噩耗,因为需要针对不同的浏览器进行不同的适配;
ECMAScript 标准规范 ECMA-262:
1996 年 11 月,网景正式向 ECMA(欧洲计算机制造商协会)提交语言标准。
- 1997 年 6 月,ECMA 以 JavaScript 语言为基础制定了 ECMAScript 标准规范ECMA-262;
- ECMA-262 是一份标准,定义了 ECMAScript;
- JavaScript 成为了 ECMAScript 最著名的实现之一;
- 除此之外,ActionScript 和 JScript 也都是 ECMAScript 规范的实现语言;
ECMAScript 是一种规范,而 JavaScript 是这种规范的一种实现。
JS 组成
ECMAScript 是 JavaScript 的标准,描述了该语言的语法和基本对象。
- JavaScript 是ECMAScript的语言层面的实现;
- 因为除了语言规范之外,JavaScript 还需要对页面和浏览器进行各种操作;
- 除了基本实现之外,还包括DOM操作和BOM操作;
目前我们会针对性的学习 ECMAScript,也就是语言层面的内容,特别是 ES5 之前的语法。


JS 应用

基础
编写方式
编写方式:
位置一:HTML 代码行内(不推荐)

位置二:Script 标签中

位置三:外部的 Script 文件
需要通过 script 元素的src属性来引入 JavaScript 文件


注意事项:
script 元素不能写成单标签
在外联式引用 js 文件时,script 标签中不可以写 JavaScript 代码,

script 标签不能写成单标签,即不能写成:
<script src="index.js"/>;
现代浏览器可以省略 type 属性
- 在以前的代码中,
<script>标签中会使用type="text/javascript"; - 现在可不写这个代码了,因为 JavaScript 是所有现代浏览器以及 HTML5 中的默认脚本语言;
- 在以前的代码中,
加载顺序
- 作为 HTML 文档内容的一部分,JavaScript默认遵循 HTML 文档的加载顺序(自上而下)。
- 推荐将 JavaScript 代码和编写位置放在 body 子元素的最后一行;
严格区分大小写
- HTML 元素和 CSS 属性不区分大小写,但是在 JavaScript 中严格区分大小写;
后续补充:script 元素还有 defer、async 属性,我们后续再详细讲解。
<noscript>
针对早期浏览器不支持 JavaScript 的问题,需要一个页面优雅降级的处理方案:<noscript>。
<noscript>:定义在浏览器不支持或禁用 JS 时显示的替代内容。
语法:
- 位置:可放置在
<head>或<body>中。 - 内容:可包含任何 HTML 元素(如文本、图片、链接等)。
<noscript>
<!-- 替代内容 -->
</noscript>示例
在
<body>中(常见用法)html<script> document.write('JavaScript 已启用!') </script> <noscript> <p>⚠️ 您的浏览器未启用 JavaScript,部分功能无法使用!</p> </noscript>在
<head>中(特殊用途):仅用于加载 CSS 等资源html<head> <noscript> <link rel="stylesheet" href="no-js-styles.css" /> </noscript> </head>
浏览器禁用 JS:
路径:浏览器 - 设置 - 隐私与安全 - 网站设置 - JavaScript

交互方式
交换方式:JavaScript 有如下和用户交互的手段:最常见的是通过 console.log, 目前大家掌握这个即可;

Chrome 调试
Console
在前面我们利用 Chrome 的调试工具来调试了 HTML、CSS,它也可以帮助我们来调试 JavaScript。
当我们在 JavaScript 中通过console 函数显示一些内容时,也可以使用 Chrome 浏览器来查看:

注意事项:
- 显示错误:如果在代码中出现了错误,那么可以在 console 中显示错误。
- 命令行:console 中有个
>标志,它表示控制台的命令行。- 单行命令:在命令行中我们可以直接编写 JavaScript 代码,按下
enter会执行代码。 - 多行命令:如果希望编写多行代码,可以按下
shift+enter来进行换行编写。
- 单行命令:在命令行中我们可以直接编写 JavaScript 代码,按下
- 后续:debug:在后续我们还会学习如何通过 debug 方式来调试、查看代码的执行流程;
语句和分号
语句(Statements):是向浏览器发出的指令,通常表达一个操作或者行为(Action)。
比如我们前面编写的每一行代码都是一个语句,用于告知浏览器一条执行的命令;

分号(Semicolon):通常每条语句的后面我们会添加一个分号,表示语句的结束:
自动插入分号(an automatic semicolon,省略分号):当存在换行符(line break)时,在大多数情况下可以省略分号。JS 将换行符理解成“隐式”的分号;
推荐做法:
- 前期:在对 JavaScript 语法不熟悉的情况推荐添加分号;
- 后期:对 JavaScript 语法熟练的情况下,任意!
注释
在 HTML、CSS 中我们都添加过注释,JavaScript 也可以添加注释。
JavaScript 的注释主要分为三种:
单行注释:
// 单行注释。多行注释:
/* 多行注释 */。文档注释:VSCode 中需要在单独的 JavaScript 文件中编写才有效。

注意:JS 不支持注释的嵌套。
VSCode 插件
推荐两个 VSCode 插件:
ES7+ React/Redux/React-Native snippets:这个插件是在 react 开发中会使用到的,但是我经常用到它里面的打印语句;
Bracket Pair Colorizer 2:VSCode 已内置,配置方法:
json"editor.bracketPairColorization.enabled": true, "editor.guides.bracketPairs":"active"
变量
概述
变量(Variable):用于存储数据的命名容器。程序通过变量名访问和操作内存中的数据。
程序中变量的数据:
在我们平时开发中,使用最多的并不是固定的数据, 而是会变换的数据:
- 比如购物车商品的数量、价格的计算等等;
- 比如一首歌曲播放的时间、进度条、歌词的展示等等;
- 比如微信聊天中消息条数、时间、语音的长度、头像、名称等等;
- 比如游戏中技能的冷却时间、血量、蓝量、buff 时间、金币的数量等等;

如果我们希望记录某一个之后会变量的数据,在 JavaScript 中我们可以定义一个 变量:
- 一个变量,就是一个用于存放数值的容器;
- 这个数值可能是一个用于计算的数字,或者是一个句子中的字符串,或者其他任意的数据;
- 变量的独特之处在于它存放的数值是可以改变的;
我们可以把变量想象成一个盒子,盒子里面装着我们的数据,我们需要给盒子进行一个特性的名称。
- 例如,变量 message 可以被想象成一个标有 “message” 的盒子,盒子里面的值为 “Hello!”;
- 并且,这个盒子的值,我们想改变多少次,就可以改变多少次;


语法格式
语法格式:在 JavaScript 中如何命名一个变量呢?包含两部分:
变量的声明:在 JavaScript 中声明一个变量使用var关键字(variable 单词的缩写)(后续学习 ES6 还有 let、const 声明方式)
var message变量的赋值:使用 = 给变量进行赋值;
var message = 'hello'核心特性:
存储的值可变:同一个变量在不同时刻可存储不同值。
jsvar score = 100 // 初始值 score = 95 // 更新值声明和赋值分开操作:
jsvar message message = 'hello'同时声明多个变量:
js// 写法一: var name, age, height name = 'tom' age = 18 height = 1.88 // 写法二: var name = 'tom', age = 18, height = 1.88// 写法三:(推荐) var name = 'tom' var age = 18 var height = 1.88
命名规范
命名标识符规则:必须遵守
- 字母/下划线开头:包含字母、数字、下划线(如
user_age,_count,totalPrice)。 - 区分大小写:
myVar≠myvar。 - 不可用关键字:避免
if,for,function等语言保留字。
命名标识符规范:建议遵守
- 驼峰写法:多个单词使用驼峰标识;
- 空格:赋值 = 两边都加上一个空格;
- 分号:一条语句结束后加上分号; 也有很多人的习惯是不加;
- 见名知意:变量应该做到见名知意;

练习
练习一:定义一些变量,保存自己的个人信息:
- 比如姓名、年龄、身高、体重、爱好等等
练习二:定义一个变量 name,赋值成 coderwhy。定义一个变量 admin,将 name 赋值给 admin
练习三:定义变量,保存两个数字,并且对两个变量的数字进行交换
- 方式一:使用临时变量
- 方式二:不使用临时变量(了解即可)
练习四:让用户在浏览器中输入一个值,在 JavaScript 程序中使用变量接收
作业:你平时在使用一些应用程序时,哪些内容可以定义成变量?
- 比如玩游戏、听歌、购物的应用程序中;
使用注意
注意 1:变量未声明
如果一个变量未声明(declaration)就直接使用,那么会报错。

注意 2:变量未赋值
如果一个变量有声明,但是没有赋值,那么默认值是undefined。

注意 3:不用 var 声明变量
如果没有使用 var 声明变量也可以,但是不推荐(事实上会被添加到 window 对象上)

数据类型
JS 的数据类型
JavaScript 中的值都具有特定的类型:
- 例如,字符串或数字。
- 我们可以将值赋值给一个变量,那么这个变量就具备了特定的类型;
动态类型的编程语言(dynamically typed):允许一个变量可以在前一刻是个字符串,下一刻就存储一个数字的编程语言,如 JavaScript。
数据类型分为两大类:
- 原始类型(Primitive):存储简单数据值,不可变(immutable),按值传递。
- 对象类型(Object):存储复杂数据结构,按引用传递,包含属性和方法。
JS 中有 8 种基本数据类型:7 种原始类型和 1 种引用类型
- Number:任何类型的数字,整数或浮点数。
- String:字符串。
- Boolean:true 和 false。
- Undefined:未定义的值,只有一个
undefined值的独立类型。 - Null:未知的值,只有一个
null值的独立类型。 - Object:更复杂的数据结构。
- BigInt:任意长度的整数。
- Symbol:唯一的标识符。
Number
Number:代表整数和浮点数。

常见操作:数字 number 可以有很多操作,比如,乘法 *、除法 /、加法 +、减法 - 等。
常见运算符:后续专门讲解。

特殊数值(Special Numeric Values):除了常规的数字,还包括所谓的特殊数值也属于 Number 类型。
Infinity:代表数学概念中的无穷大∞,也可以表示-Infinity。如1/0 == Infinity。NaN:代表一个计算错误,它是一个错误的操作所得到的结果;如'tom' * 100 == NaN。
进制表示:在之前我们学习过进制的概念,数字类型也有其他的进制表示方法:
- 十进制
- 十六进制:
0x开头 - 二进制:
0b开头 - 八进制:
0o开头

数字表示的范围:
- 最小正浮点数:
Number.MIN_VALUE,这个值为:5e-324,小于这个的数字会被转化为 0。 - 最大正浮点数:
Number.MAX_VALUE,这个值为:1.7976931348623157e+308。
相关方法:
- Number.isNaN():
(num),用于判断是否不是一个数字。不是数字返回 true,是数字返回 false。
后续我们会对 Number 类型进行更加详细的学习;
String
String:用于表示和操作字符序列。
在开发中我们经常会有一些文本需要表示,这个时候我们会使用字符串 String:
- 比如人的姓名:coderwhy。地址:广州市。简介:认真是一种可怕的力量;
字符串字面量:可以使用单引号' 、 双引号" 或 反引号指定。

前后引号类型必须一致:
- 如果在字符串里面本身包括单引号,可以使用双引号;
- 如果在字符串里面本身包括双引号,可以使用单引号;

转义字符
转义字符:除了普通的可打印字符以外,一些有特殊功能的字符可以通过转义字符的形式放入字符串中:

转义字符串开发中只有特殊场景才会用到,暂时掌握 \' \" \t \n四个的用法即可。
属性和方法
字符串还有很多细节和操作方法,在后续学习了面向对象后,我们再详细学习;
基本操作:这里我们先掌握几个基本的字符串使用操作:
字符串拼接:通过
+运算符(后续还会详细讲解)
获取字符串长度

Boolean
Boolean:布尔,用于表示真假:
- 比如是否毕业. 是否有身份证. 是否购买车票. 是否成年人;
- 比如开发中,我们会判断一个账号是否登录、是否是管理员、是否具备某个权限、是否拥有某个英雄、皮肤等;
命令来源:布尔(英语:Boolean)是计算机科学中的逻辑数据类型,以发明布尔代数的数学家乔治·布尔为名。
Boolean 类型的值:true 和 false。

在后续 逻辑运算符 中我们还会详细学习和使用 Boolean 类型;
Undefined
Undefined:未定义,表示变量已声明但尚未赋值,或对象属性/函数参数不存在。
Undefined 类型的值:undefined。
示例:
未赋值的变量

显式赋值 undefined(不推荐)

注意事项:
- 推荐变量定义时初始化:而不只是声明一个变量。
- 不要显式为变量赋值
undefined:可以赋值0、''、null表示什么变量开始时都没有。
Null
Null:通常用来表示一个对象为空。
Null 类型的值:null。
应用场景:
初始化对象变量:给一个对象进行初始化时赋值为 null。
jsvar obj = null // 稍后会被赋值 obj = { name: 'tom', age: 18 }typeof 检测
jstypeof null // "object"
对比 undefined:
| 对比项 | null | undefined |
|---|---|---|
| 含义 | "无对象"的故意空值 | "未初始化"的系统默认值 |
| 类型 | typeof null → "object"(历史遗留错误) | typeof undefined → "undefined" |
| 赋值方 | 开发者显式赋值 | JavaScript 引擎自动分配 |
| 相等判断 | null == undefined → true | null === undefined → false |
| 典型用法 | 表示有意的空对象引用 | 表示未定义的初始状态 |
Object
Object:引用类型,用于存储键值对集合。几乎所有复杂数据类型(如数组、函数)都是对象的特殊形式。
对象类型包括以下特殊类型:
| 类型 | 说明 | 检测方式 |
|---|---|---|
| Object | {} 或 new Object() | typeof obj === "object" |
| Array | [1, 2, 3] | Array.isArray(arr) |
| Function | function() {} | typeof func === "function" |
| Date | new Date() | obj instanceof Date |
| RegExp | /pattern/ | obj instanceof RegExp |
对象的字面量语法:
// 对象字面量语法
const person = {
name: '张三', // 键(key): "name" → 值(value): "张三"
age: 30, // 键值对 = 属性(property)
isEmployed: true,
sayHello() {
// 方法(method)
console.log(`你好,我是${this.name}`)
}
}相关操作
typeof
typeof:用于检测变量或表达式数据类型的一元操作符。它返回一个表示数据类型的字符串值。
语法:
// 两种使用方式
typeof operand
typeof operandoperand:要检测的变量或表达式。接受任何 JS 数据类型作为参数。- 括号:可选,
typeof x和typeof(x)效果相同。typeof 是一个操作符,并非是一个函数,()只是将后续的内容当做一个整体而已。
返回值:始终返回表示数据类型的字符串
- 特殊值:只有
Null/Array类型或Set/WeakSet/Map/WeakMap类型的返回值不是自身类型的字符串描述,而是"object"
| 操作数类型 | 返回值 | 补充判断方法 | 示例 |
|---|---|---|---|
| Number | "number" | typeof 3.14 / typeof NaN | |
| String | "string" | typeof "hello" | |
| Boolean | "boolean" | typeof true | |
| Undefined | "undefined" | typeof undefined | |
| Object | "object" | typeof {} | |
| BigInt | "bigint" | typeof 10n | |
| Symbol | "symbol" | typeof Symbol() | |
| Function | "function" | typeof alert | |
| 特殊值: | |||
| Null | "object" 🚨 | val === Null | typeof null (历史遗留问题) |
| Array | "object" 🚨 | Array.isArray() | typeof [] |
| Set | "object" 🚨 | instanceof Set | typeof new Set([1,2,3]) |
| WeakSet | "object" 🚨 | instanceof WeakSet | typeof new WeakSet([{msg:'mr'},{age: 18}]) |
| Map | "object" 🚨 | instanceof Map | typeof new Map([[100,'aaa'],[{msg:'mr'},'bbb']]) |
| WeakMap | "object" 🚨 | instanceof WeakMap | typeof new WeakMap([[{msg:'mr'}, 'aaa']]) |
示例:类型检查
const typeCheck = (value) => {
if (value === null) return 'null' // 区分null
const baseType = typeof value
// 区分数组和普通对象
return baseType === 'object'
? Array.isArray(value)
? 'array'
: 'object' // 区分array
: baseType
}
// 测试
typeCheck(42) // 'number'
typeCheck(null) // 'null'
typeCheck([1, 2]) // 'array'
typeCheck({ a: 1 }) // 'object'
typeCheck(() => {}) // 'function'数据类型转换
需求:在开发中,我们可能会在不同的数据类型之间进行某些操作
- 比如把一个 String 类型的数字和另外一个 Number 类型的数字进行运算;
- 比如把一个 String 类型的文本和另外一个 Number 类型的数字进行相加;
- 比如把一个 String 类型或者 Number 类型的内容,当做一个 Boolean 类型来进行判断;
- 等等
转换方式:也就是在开发中,我们会经常需要对数据类型进行转换:
- 隐式转换:大多数情况下,运算符和函数会自动将赋予它们的值转换为正确的类型,这是一种隐式转换;
- 显式转换:我们也可以,通过显式的方式来对数据类型进行转换;
接下来我们来看一下数据类型之间的转换:
- String、Number、Boolean 类型;
String 转换
其他类型经常需要转换成字符串类型,比如和字符串拼接在一起或者使用字符串中的方法。
隐式转换:
+操作:一个字符串和另一个字符串进行+操作;- 如果
+运算符左右两边有一个是字符串,那么另一边会自动转换成字符串类型进行拼接;
- 如果
函数执行:某些函数的执行也会自动将参数转为字符串类型。如 console.log()函数;
显式转换:
- 调用String()函数;
- 调用toString()方法(后续面向对象再学习);
方法和函数的区别,我们后续在讲解面向对象时会讲到;
Number 转换
其他类型也可能会转换成数字类型。
隐式转换:
- 算数运算:在算数运算中,通常会将其他类型转换成数字类型来进行运算。如
"6" / "2"; +运算特殊:但是如果是+运算,并且其中一边有字符串,那么还是按照字符串来连接的;
显式转换:
- 使用Number()函数来进行显式的转换;
类型转换规则:

Boolean 转换
布尔(boolean)类型转换是最简单的。
隐式转换:发生在逻辑运算中。
显式转换:调用 Boolean(value) 显式地进行转换。
转换规则:
false:直观上为“空”的值(如 0、空字符串、null、undefined 和 NaN)将变为 false。true:其他值变成 true。- 特殊值
"0":包含 0 的字符串 "0" 是 true- 一些编程语言(比如 PHP)视 "0" 为 false。但在 JavaScript 中,非空的字符串总是 true。

JS 真值和假值
在 JavaScript 中:
- 假值(Falsy):
false,0,-0,0n,"",null,undefined,NaN。 - 真值(Truthy):除假值外的所有值(包括
{},[],"0","false"等)。
运算符
概述
运算符
在小学的时候我们就学习了各种运算符,比如加号 +、乘号 *、减号 - 、除号/
几乎所有的编程语言都有各种各样的运算符(Operators,操作符)
- 初次接触这些运算符, 你会感觉种类繁多, 难以记忆.
- 但是并不需要特别担心, 因为很多的运算符我们在以后的开发中, 每天都会使用;
- 多练习, 不需要刻意去记忆;
- 而且常见的高级语言运算符都是相似的,学了 JavaScript 运算符很容易掌握 C/C++/OC/Python 等语言的运算符;
计算机最基本的操作就是执行运算,执行运算时就需要使用运算符来操作:
- 比如 console.log(20 + 30);
+号就是一个运算符. - 比如 console.log(20 _ 30);
_号也是一个运算符.
运算符分类:JavaScript 按照使用场景的不同将运算符分成了很多种类型:
- 算术运算符:
+、-、*、/、%、**、++、-- - 赋值运算符:
=、+=、-=、*=、/=、%=、**=、??= - 比较运算符:
==、===、!=、!==、>、<、>=、<=、? - 逻辑运算符:
&&、||、!、?? - 位运算符:
&、|、^、~、<<、>>、>>> - 三元运算符:
?: - 类型运算符:
typeof、instanceof、in、delete - 其他运算符:
,、void、new、...、()
运算符优先级:从高到低排列(部分关键优先级):
| 优先级 | 运算符类型 | 运算符示例 | | | | :----- | :--------------- | :---------------------------------- | --- | --- | --- | --- | | 21 | 分组 | () | | | | 20 | 成员访问 | . [] | | | | 19 | new (带参数列表) | new Date() | | | | 18 | 函数调用 | fn() | | | | 17 | 后置递增/减 | ++ -- | | | | 16 | 逻辑非/位非 | ! ~ | | | | 15 | 乘除/取模 | * / % | | | | 14 | 加减 | + - | | | | 13 | 位移 | << >> >>> | | | | 12 | 关系 | < > <= >= in instanceof | | | | 10 | 相等 | == != === !== | | | | 6 | 逻辑与 | && | | | | 5 | 逻辑或 | | | | | | | 4 | 三元运算符 | ?: | | | | 3 | 赋值 | = += -= 等 | | | | 2 | yield | yield | | | | 1 | 逗号 | , | | |
运算元
在正式开始运算之前,我们先学习一下常见的术语:
运算元(Operand,参数):是指被运算符操作的值或变量(如5 * 2有 2 个运算元5和2)。
运算符:对运算元执行操作的符号(如 +, -, *, / 等)。它分为:
一元运算符:一个运算符对应的只有一个运算元(如
-3)。二元运算符:一个运算符拥有两个运算元(如
2 + 3)。
表达式:由运算元和运算符组成的计算单元。
算术运算符
算术运算符:用于数学计算,返回数字结果(除 + 可能返回字符串)。
| 运算符 | 名称 | 示例 | 结果 | 特殊说明 |
|---|---|---|---|---|
+ | 加法/字符串连接 | 5 + 2 | 7 | 字符串连接:"a" + "b" → "ab" |
- | 减法 | 5 - 2 | 3 | |
* | 乘法 | 5 * 2 | 10 | |
/ | 除法 | 10 / 2 | 5 | 5 / 0 → Infinity |
% | 取余 | 10 % 3 | 1 | 求余数 |
** | 指数(幂) | 2 ** 3 | 8 | ES2016 新增 |
++ | 自增 | let x=5; x++ | x=6 | 前增量:++x 先增后用 |
-- | 自减 | let x=5; x-- | x=4 | 后增量:x-- 先用后减 |
示例:
'5' - '2' // 3 (自动转数字)
'5' + 2 // "52" (字符串连接)取余 %
取余(%):返回左侧操作数除以右侧操作数的余数。
示例:
console.log(13 % 5) // 3
console.log(-13 % 5) // -3
console.log(4 % 2) // 0
console.log(-4 % 2) // -0幂 **
幂(:**)等价于 Math.pow(),ES2016,返回第一个操作数取第二个操作数的幂的结果。
示例:
console.log(3 ** 4) // 81
console.log(10 ** -2) // 0.01
console.log(2 ** (3 ** 2)) // 512
console.log((2 ** 3) ** 2) // 64自增/自减
自增(++)/自减(--):一元运算符,用于对变量进行加 1 或减 1 操作。
分类:它们有前缀形式和后缀形式,行为有重要区别。
前缀形式:
++x/--x,先执行增/减操作,后返回增减后的值。jslet x = 5 let y = ++x // 1. x 变为 6 2. y 被赋值为 6 console.log(x, y) // 6, 6后缀形式:
x++/x--,先返回当前值,后执行增/减操作。jslet a = 5 let b = a-- // 1. b 被赋值为 5 2. a 变为 4 console.log(a, b) // 4, 5
核心特性:
自增/自减只能应用于变量:应用于数值(如
5++)会报错。js// x++ 返回的是值而非变量引用,不能再次应用 ++ let x = 5; x++++; // SyntaxError: Invalid left-hand side expression
赋值运算符
赋值运算符:用于将值赋给变量。
let x = 123 // 将值 123 写入 x 然后返回 x分类:
- 基础赋值运算符:
= - 算术赋值运算符:
+=、-=、*=、/=、%=、**= - 位运算赋值运算符:
&=、|=、^=、<<=、>>=、>>>= - 逻辑赋值运算符:
&&=、||=、??=
基础赋值运算符
基础赋值
基础赋值:将右侧的值赋给左侧的变量,并返回被赋的值(可链式赋值)。
语法:
variable = value示例:
// 基本赋值
let x = 10
// 链式赋值
let a, b, c
a = b = c = 5 // 所有变量值为5
// 解构赋值
const [first, second] = [1, 2] // first=1, second=2
const { name, age } = { name: 'Alice', age: 30 }链式赋值
链式赋值(Chained Assignment):允许在单个表达式中将同一个值连续赋给多个变量。
语法:
variable1 = variable2 = ... = value;执行顺序:
let a, b, c
a = b = c = 10
// 执行顺序:
// 1. c = 10 → 将10赋值给c
// 2. b = c → 将c的值(10)赋值给b
// 3. a = b → 将b的值(10)赋值给a核心特性:
- 从右向左执行:赋值操作从最右侧开始
- 共享值:所有变量获得相同的值
- 返回最终值:整个表达式返回最左侧变量的值
- 变量必须先声明(使用 let/var/const)
注意事项:
变量要先声明
js// ❌ 错误:未声明的变量 a = b = 10 // 在严格模式下报错,非严格模式创建全局变量 // ✅ 正确:先声明 let a, b a = b = 10共享同一个数组/对象引用
jsconst arr1 = (arr2 = []) // 共享同一个数组引用 arr1.push(1) console.log(arr2) // [1] - 两个变量指向同一数组 console.log(arr1 === arr2) // true更现代的替代方案:解构赋值(更强大)
js// 更现代的替代方案 const [x, y, z] = Array(3).fill(0) // 对象属性 const config = { width: 100, height: 100, depth: 100 }
算术赋值运算符
原地修改
原地修改 (In-place Modification):是指直接在原始数据结构上进行更改,而不创建新的副本。这种操作方式直接改变内存中的原始数据,而不是返回一个新的修改后的版本。
核心特性:
- 内存效率:不创建新对象,节省内存
- 副作用:操作会改变原始数据
- 性能优势:避免创建副本的开销
- 引用保留:所有指向该数据的引用都会看到变化
示例:算术复合赋值
我们经常需要对一个变量做运算,并将新的结果存储在同一个变量中。
// 原地修改
var n = 10
n = n + 5
n = n * 2
// 使用 += *= 缩写
n += 5
n *= 2算术赋值
算术赋值:将算术运算与赋值结合,简化表达式。
所有算术和位运算符都有简短的“修改并赋值”运算符:/= 和 -= 等。
| 运算符 | 示例 | 等价于 | 说明 |
|---|---|---|---|
+= | x += 5 | x = x + 5 | 加法赋值 |
-= | x -= 3 | x = x - 3 | 减法赋值 |
*= | x *= 2 | x = x * 2 | 乘法赋值 |
/= | x /= 4 | x = x / 4 | 除法赋值 |
%= | x %= 3 | x = x % 3 | 取模赋值 |
**= | x **= 2 | x = x ** 2 | 指数赋值 (ES2016) |
示例:特殊行为
// 字符串操作
let str = 'Hello'
str += ' World!' // "Hello World!"
// 类型转换
let num = '5'
num *= 2 // 10 (自动转数字)
// 浮点数问题
let price = 19.99
price *= 100 // 1998.9999999999998 (精度问题)位运算赋值运算符@
位运算赋值运算符(位运算复合赋值运算符):用于底层二进制操作。
| 运算符 | 示例 | 等价于 | 说明 |
|---|---|---|---|
&= | x &= 5 | x = x & 5 | 按位与赋值 |
|= | x |= 3 | x = x | 3 | 按位或赋值 |
^= | x ^= 1 | x = x ^ 1 | 按位异或赋值 |
<<= | x <<= 2 | x = x << 2 | 左移赋值 |
>>= | x >>= 1 | x = x >> 1 | 带符号右移赋值 |
>>>= | x >>>= 1 | x = x >>> 1 | 无符号右移赋值 |
示例:应用场景
// 权限系统
let permissions = 0
const READ = 1
const WRITE = 2
// 添加写权限
permissions |= WRITE
// 检查读权限
const canRead = (permissions & READ) === READ逻辑赋值运算符
逻辑赋值运算符(逻辑复合赋值运算符):针对逻辑运算的增强赋值。
| 运算符 | 示例 | 等价于 | 说明 |
|---|---|---|---|
&&= | x &&= y | x = x && y | 逻辑与赋值。仅在 x 为真值时为其赋值(很少用) |
||= | x ||= y | x = x || y | 逻辑或赋值。仅在 x 为假值时为其赋值 |
??= | x ??= y | x = x ?? y | 逻辑空赋值。仅在 x 为 空值(null 或 undefined) 时为其赋值 |
示例:
基本应用:设置默认值
js// ||= 设置默认值(null/undefined/0/false/''都会触发默认值) function foo(msg) { msg ||= '默认值' // 等价于:msg = msg || '默认值' } // ??= 设置默认值(更严谨,只有null或undefined触发默认值) function foo(msg) { msg ??= '默认值' // 等价于:msg = msg ?? '默认值' }??=的应用场景:判断 obj 是否有值,如果有值将 obj.name 赋值给 obj
jslet obj = { name: 'tom' } obj &&= obj.name // 等价于 obj = obj && obj.name console.log(obj) // 'tom'特殊行为
js// ||= 和 ??= 的区别 let count = 0 count ||= 10 // 0 是 false → count=10 count ??= 20 // 0 不是 null/undefined → 保持0 // 实际应用:配置默认值 const config = {} config.timeout ??= 5000 // undefined:仅当未定义时设置
解构赋值@
解构赋值(Destructuring Assignment):ES2015,从数组或对象中提取数据,并直接赋值给变量。它分为数组解构和对象解构。
核心特性:
- 解包:从数组或对象中提取值。
- 赋值:将提取的值赋给相应的变量。
- 模式匹配:根据特定模式匹配数据结构。
数组解构
数组解构(Array Destructuring):ES2015,从数组中提取值,并将这些值直接赋给变量。
语法:
const colors = ['red', 'green', 'blue']
// 基础解构
const [firstColor, secondColor, thirdColor] = colors
console.log(firstColor) // 'red'
console.log(secondColor) // 'green'
console.log(thirdColor) // 'blue'核心特性:
顺序问题:数组解构有严格的顺序问题,参数一一对应数组中的元素
jsconst numbers = [1, 2, 3, 4, 5] const [first, second, third] = numbers // 1 2 3跳过不需要的元素
jsconst numbers = [1, 2, 3, 4, 5] // 跳过不需要的元素 const [first, , third, , fifth] = numbers console.log(first) // 1 console.log(third) // 3 console.log(fifth) // 5设置默认值:当解构的值可能为 undefined 时,可以设置默认值
jsconst fruits = ['apple', 'banana'] // 设置默认值 const [fruit1, fruit2, fruit3 = 'orange'] = fruits console.log(fruit1) // 'apple' console.log(fruit2) // 'banana' console.log(fruit3) // 'orange' (使用默认值)剩余参数:使用剩余参数 (
...) 捕获数组剩余部分jsconst letters = ['a', 'b', 'c', 'd', 'e'] // 获取剩余元素 const [first, second, ...rest] = letters console.log(first) // 'a' console.log(second) // 'b' console.log(rest) // ['c', 'd', 'e']
对象解构
对象解构:ES2015,从对象中提取值,并将这些值直接赋给变量。
语法:
const user = {
id: 1,
username: 'js_dev',
email: 'dev@example.com'
}
// 解构基本属性
const { username, email } = user
console.log(username) // 'js_dev'
console.log(email) // 'dev@example.com'核心特性:
顺序问题:对象解构没有顺序问题,是根据 key 来解构的。
jsconst obj = { name: 'tom', age: 18, heigth: 1.88 } const { height, age, name } = obj console.log(name, age, height) // 'tom' 18 1.88重命名变量
jsconst book = { title: 'JavaScript: The Good Parts', author: 'Douglas Crockford' } // 重命名解构出的变量 const { title: bookTitle, author: bookAuthor } = book console.log(bookTitle) // 'JavaScript: The Good Parts' console.log(bookAuthor) // 'Douglas Crockford'设置默认值:当属性不存在时使用默认值
jsconst settings = { theme: 'dark', fontSize: 16 } // 设置默认值(当属性不存在时使用) const { theme, fontSize, notifications = true } = settings console.log(notifications) // true(使用默认值)嵌套解构
jsconst employee = { id: 101, name: 'Alice', department: { name: 'Engineering', location: 'Building A' } } // 嵌套解构 const { name, department: { location } } = employee结合重命名和默认值
jsconst product = { name: 'Laptop', price: 999 } // 组合使用重命名和默认值 const { name: productName, category = 'Electronics', stock: availableStock = 0 } = product剩余参数
jsconst student = { name: 'Bob', age: 20, major: 'Computer Science', gpa: 3.8 } // 获取剩余属性 const { name, ...academicInfo } = studentconsole.log(name) // 'Bob' console.log(academicInfo) // { age: 20, major: 'Computer Science', gpa: 3.8 } 动态属性名解构
jsconst config = { apiUrl: 'https://api.example.com', timeout: 5000 } const property = 'apiUrl' const { [property]: apiEndpoint } = config console.log(apiEndpoint) // 'https://api.example.com'不修改原始对象
jsconst original = { a: 1, b: 2 } // 解构不会修改原始对象 const { a } = original console.log(original) // { a: 1, b: 2 } (未改变)
应用场景:
获取坐标
jsfunction getPosition({ x, y }) { console.log(x, y) } getPosition({ x: 10, y: 20 })
比较运算符
比较运算符:用于比较两个值,返回布尔值(true或false)表示比较结果。
分类:
- 相等性比较运算符:
- 关系比较运算符:
- 特殊比较运算符:
相等性比较
| 运算符 | 名称 | 示例 | 结果 | 说明 |
|---|---|---|---|---|
== | 宽松相等 | '5' == 5 | true | 执行类型转换后比较值 |
=== | 严格相等 | '5' === 5 | false | 不执行类型转换,要求类型和值都相同 |
!= | 宽松不等 | '5' != 5 | false | 执行类型转换,检查不相等 |
!== | 严格不等 | '5' !== 5 | true | 不执行类型转换,检查不相等 |
特殊值 NaN:唯一不等于自身的值
// 唯一不等于自身的值
NaN == NaN // false
NaN === NaN // false问题:任何值和 NaN 比较,永远为false
// 错误
value === NaN // 永远false解决:使用 Number.isNaN() 或 Object.is()进行比较
// 正确
Number.isNaN(value)
Object.is(value, NaN)对比 === 和 ==:
问题:==存在一个问题,它不能区分出 0 和 false,或者空字符串和 false,因为它会进行隐式类型转换,false 或空字符串都会被转换为数字 0。
- 类型转换:
===:不进行类型转换;==:隐式进行类型转换。 - 比较方式:
===:比较值和类型;==:比较转换后的值。 - 性能:
===:更快;==:稍慢。 - 可预测性:
===:高;==:低。 - 推荐使用:
===
关系比较
| 运算符 | 名称 | 示例 | 结果 |
|---|---|---|---|
> | 大于 | 10 > 2 | true |
< | 小于 | 10 < 2 | false |
>= | 大于等于 | 5 >= 5 | true |
<= | 小于等于 | 5 >= 5 | true |
比较规则:
数字:直接比较数值
js10 > 5 // true 3 < 2 // false字符串:字典序比较
js'b' > 'a' // true '2' > '10' // true ('2'字符码50 > '1'字符码49)混合类型:转换为数字比较
js'10' > 5 // true (字符串'10'转数字10) true > 0.5 // true (true转1)
特殊比较
| 运算符 | 名称 | 示例 | 结果 |
|---|---|---|---|
in | 检查对象是否包含指定属性 | 'name' in {name: 'tom'} | true |
instanceof | 检查对象是否为特定构造函数的实例 | [] instanceof Array | true |
逻辑运算符
逻辑运算符:用于处理布尔逻辑。它们不仅能操作布尔值,还能处理各种数据类型,并利用短路求值特性实现高效的条件判断。
| 运算符 | 名称 | 语法 | 规则 |
|---|---|---|---|
&& | 逻辑与 | expr1 && expr2 | 如果 expr1 可转换为 false,返回 expr1;否则返回 expr2 |
|| | 逻辑或 | expr1 || expr2 | 如果 expr1 可转换为 true,返回 expr1;否则返回 expr2 |
! | 逻辑非 | !expr | 将操作数转换为布尔值后取反 |
??(ES2020) | 逻辑空 | expr1 ?? expr2 | 当 expr1 是 null 或 undefined 时返回 expr2;否则返回 expr1 |
逻辑与 &&
逻辑与(Logical AND):&&,用于检查两个或多个表达式是否同时为真:
- 如果所有操作数都为真值,返回最后一个真值。
- 如果任一操作数为假值,返回第一个假值。
语法:
result = expression1 && expression2示例:
真值/假值处理
jsconsole.log(5 && 3) // 3 (两个真值) console.log(0 && 2) // 0 (第一个假值) console.log('' && 'test') // "" (第一个假值) console.log(null && 10) // null (第一个假值)
短路求值(Short-Circuiting):逻辑与运算符具有短路特性:
- 如果第一个操作数为假值,不计算第二个操作数。
- 只有第一个操作数为真值时,才计算第二个操作数。
function logMessage() {
console.log('函数被执行')
return true
}
false && logMessage() // 无输出(函数未执行)
true && logMessage() // "函数被执行"运算符优先级:&& 优先级高于 ||
// && 优先级高于 ||
const result = true || (false && false) // true (相当于 true || (false && false))
// 使用括号明确意图
const safeResult = (true || false) && false // false逻辑或 ||
逻辑或(Logical OR):||,用于检查两个或多个表达式中是否至少有一个为真:
- 如果任一操作数为真值,返回第一个真值。
- 如果所有操作数都为假值,返回最后一个假值。
语法:
result = expression1 || expression2示例:
真值/假值处理
jsconsole.log(5 || 3) // 5 (第一个真值) console.log(0 || 2) // 2 (第二个真值) console.log('' || 'test') // "test" (第二个真值) console.log(null || 10) // 10 (第二个真值) console.log(undefined || null || 0) // 0 (所有操作数都是假值,返回最后一个)设置默认值(最常见用法)
js// 配置默认端口 const port = process.env.PORT || 8080 // 默认用户设置 const userSettings = user.customSettings || defaultSettings // 默认消息 const message = inputMessage || '没有可用消息'
短路求值(Short-Circuiting):逻辑或运算符具有短路特性:
- 如果第一个操作数为真值,不计算第二个操作数。
- 只有第一个操作数为假值时,才计算第二个操作数。
function logMessage() {
console.log('函数被执行')
return true
}
true || logMessage() // 无输出(函数未执行)
false || logMessage() // "函数被执行"避免假值被覆盖:
问题:
0或""是有效值但被覆盖js// 问题:0 是有效值但被忽略 const quantity = 0 const display = quantity || '未知' // "未知"解决方案:
方案一:使用空值合并运算符 (??)
js// 解决方案:使用空值合并运算符 (??) const correctDisplay = quantity ?? '未知' // 0方案二:明确检查
js// 解决方案:明确检查 const finalMessage = message === '' ? '' : message || '默认消息'
逻辑非 !
逻辑非(Logical NOT):!,对其操作数的布尔值进行取反:
- 如果操作数为 真值(truthy),返回
false。 - 如果操作数为 假值(falsy),返回
true。
语法:
const result = !expression示例:
真值/假值转换
jsconsole.log(!0) // true (0 是假值) console.log(!1) // false (1 是真值) console.log(!'') // true (空字符串是假值) console.log(!'hello') // false (非空字符串是真值) console.log(!null) // true console.log(!undefined) // true console.log(![]) // false (数组是真值) console.log(!{}) // false (对象是真值)
双重非:!!,快速将任何值转换为布尔类型。
console.log(!!0) // false
console.log(!!1) // true
console.log(!!'') // false
console.log(!!'hello') // true
console.log(!!null) // false
console.log(!!undefined) // false
console.log(!![]) // true
console.log(!!{}) // true性能考量:逻辑非是 JavaScript 中最快的操作之一,几乎不影响性能:
const iterations = 10000000
console.time('Logical NOT')
for (let i = 0; i < iterations; i++) {
const result = !i
}
console.timeEnd('Logical NOT')
// 典型结果:10-50ms(取决于运行环境)逻辑空 ??
逻辑空(Nullish Coalescing Operator,空值合并运算符):??,ES2020,是一个逻辑运算符:
- 当左侧操作数为
null或undefined时,返回右侧操作数。 - 当左侧操作数不是
null或undefined时,返回左侧操作数。
语法:
const result = valueToCheck ?? defaultValue示例:
安全的默认值设置
js// 用户配置处理 function loadUserSettings(settings) { const theme = settings.theme ?? 'light' const fontSize = settings.fontSize ?? 16 const notifications = settings.notifications ?? true console.log(`主题: ${theme}, 字体大小: ${fontSize}, 通知: ${notifications}`) } loadUserSettings({ theme: 'dark' }) // 主题: dark, 字体大小: 16, 通知: true结合可选链
?.:jsconst user = { profile: { name: 'Alice', address: null } } // 安全访问嵌套属性 const street = user?.profile?.address?.street ?? '街道未知' const city = user?.profile?.address?.city ?? '城市未知' console.log(street) // "街道未知" console.log(city) // "城市未知"
短路求值(Short-Circuiting):与逻辑运算符类似,?? 具有短路行为:
- 如果左侧不是
null/undefined,不计算右侧表达式。 - 只有左侧是
null/undefined时,才计算右侧。
function logMessage() {
console.log('函数被执行')
return '默认值'
}
const value1 = '有效值' ?? logMessage() // 无输出(函数未执行)
const value2 = null ?? logMessage() // "函数被执行"对比逻辑或||:
- 逻辑空:只判断左侧是否为
null/undefined。 - 逻辑或:会判断左侧是否为
null/undefined以及""、0、false、NaN等假值。
const nullValue = null
const undefinedValue = undefined
const zero = 0
const emptyString = ''
const falseValue = false
console.log(nullValue ?? '默认值') // "默认值"
console.log(undefinedValue ?? '默认值') // "默认值"
console.log(zero ?? 42) // 0
console.log(emptyString ?? '未知') // ""
console.log(falseValue ?? true) // false运算符优先级: && > || > ??,不能直接与 && 或 || 混用。
// 问题:?? 优先级低于 && 和 ||
const value = null || undefined ?? "默认"; // SyntaxError
// 解决方案:使用括号
const value = (null || undefined) ?? "默认"; // "默认"分支语句
概述
分支结构
分支结构:是编程中的基本控制结构,允许程序根据特定条件判断选择不同的执行路径。几乎所有的编程语言都有分支结构。
程序是生活的一种抽象, 只是我们用代码表示了出来
- 在开发中, 我们经常需要根据一定的条件, 来决定代码的执行方向
- 如果 条件满足,才能做某件事情
- 如果 条件不满足,就做另外一件事情
JS 中常见的分支结构:
- if 分支结构
- switch 分支结构
程序执行顺序
程序执行顺序:在程序开发中,程序有三种不同的执行顺序
- 顺序:从上向下,顺序执行代码。
- 分支:根据条件判断,决定执行代码的分支。
- 循环:让 特定代码 重复 执行。

代码块
代码块(Code Block):是 JavaScript 中由花括号 {} 包围的一组语句,它定义了一个独立的执行单元和作用域边界。
语法:
{
// 语句1
// 语句2
// ...
}作用域管理:
- 块级作用域:使用
let和const声明的变量仅在代码块内部可见。 - 词法环境:每个代码块创建新的词法环境
语句分组:在开发中,一行代码很难完成某一个特定的功能,我们就会将这些代码放到一个代码块中。

应用场景:
- 控制流语句(if/for/while):在 JS 中,我们可以通过流程控制语句来决定如何执行一个代码块:
- 通常会通过一些关键字来告知 JS 引擎代码要如何被执行;
- 比如分支语句、循环语句对应的关键字等;
- 函数定义
- 独立作用域
- 模块模式(立即执行函数)
生活中的条件判断
现实生活中有很多情况, 我们要根据条件来做一些决定:
- 小明妈妈说: 如果小明考试了 100 分, 就去游乐场(判断分数等于 100 分)
- 网吧禁止未成年人入内(判断年龄大于等于 18 岁,是否带身份证,是否带钱)
- 开发中,登录成功:账号和密码正确 或 扫描二维码成功

if 语句
if 分支结构有三种:
if
if...else
if...else if...else
if
if:是 JavaScript 中最基础且最重要的条件控制语句,用于根据特定条件的真假执行不同的代码路径。
语法:
if (condition) {
// 条件为真时执行的代码
}核心特性:
- 省略
{}:如果代码块中只有一行代码,那么{}可以省略。 - 条件表达式:必须返回布尔值或可转换为布尔值的值。
- JS 中的假值:
false,0,"",null,undefined,NaN。其他值都为真值。

案例一:如果小明考试超过 90 分, 就去游乐场
- “如果”相当于 JavaScript 中的关键字 if
- 分数超过 90 分是一个条件(可以使用 > 符号)
案例二:单位 5 元/斤的苹果,如果购买超过 5 斤,那么立减 8 元
- 注意:这里我们让用户输入购买的重量,计算出最后的价格并且弹出结果
if...else
if...else:else 可选,如果条件为假时,执行 else 分支中的代码。
语法:
if (condition) {
// 条件为真时执行
} else {
// 条件为假时执行
}
案例一:如果分数超过 90 分去游乐场,否则去上补习班
- 满足条件时,去游乐场。
- 不满足(else),去上补习班。
案例二:m=20,n=30,比较两个数字的大小,获取较大的那个数字
if (m > n) {
return m
} else {
return n
}if...else if...else
if...else if...else: else if 可多个,如果需要判断多个条件,可以使用 else if。
语法:
if (condition1) {
// 条件1为真时执行
} else if (condition2) {
// 条件2为真时执行
} else {
// 所有条件为假时执行
}
案例: 分数评级
- 考试分数大于 90:优秀
- 大于 80 小于等于 90:良好
- 大于 60 小于等于 80:合格
- 小于 60 分:不及格
if (score > 90) console.log('优秀')
else if (score > 80) console.log('良好')
else if (score > 60) console.log('及格')
else console.log('不及格')三元运算符
三元运算符:是 JS 中一种简洁的条件表达式,它提供了一种在单行中实现简单 if-else 逻辑的方法。作为 JS 中唯一需要三个操作数的运算符,它因其结构而被称为"三元运算符"。
语法:
条件 ? 表达式1 : 表达式2- 条件:计算结果为布尔值的表达式。
- 表达式 1:当条件为
true时执行的表达式。 - 表达式 2:当条件为
false时执行的表达式。
工作原理:三元运算符按以下步骤执行:
- 计算条件表达式。
- 如果条件为
true,计算并返回表达式 1的值。 - 如果条件为
false,计算并返回表达式 2的值。
示例:
变量赋值
jsconst isRaining = true const activity = isRaining ? '室内看电影' : '公园散步' console.log(activity) // "室内看电影"嵌套三元运算符:可读性差,不推荐
jsconst score = 85 const grade = score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : score >= 60 ? 'D' : 'F'console.log(grade) // "B"
对比 if...else:相同功能的不同实现,三元运算符在简单条件时更加简洁
// 使用 if-else
let message
if (user.isLoggedIn) {
message = '欢迎回来!'
} else {
message = '请登录'
}
// 使用三元运算符
const message = user.isLoggedIn ? '欢迎回来!' : '请登录'避免在表达式中修改状态:
// 不推荐:包含副作用
let counter = 0
const value = condition ? counter++ : counter--
// 推荐:避免在表达式中修改状态
let counter = 0
if (condition) {
counter++
} else {
counter--
}switch 语句
基本语法
switch:是 JS 中的多分支选择结构,用于根据表达式的不同值执行不同 case 分支的代码块。
语法:
switch (expression) {
case value1:
// expression 等于 value1 时执行
break
case value2:
// expression 等于 value2 时执行
break
// 更多 case...
default:
// 没有匹配的 case 时执行
}- switch 关键字:声明 switch 语句的开始,后跟括号
()包含要计算的表达式。 - case 子句:指定要匹配的值,使用冒号
:结束,值可以是常量、变量或表达式。 - default 子句:可选的默认分支,当没有 case 匹配时执行,通常放在最后(但不是必须)。
- break 语句:终止当前 case 的执行,防止"贯穿"(fall-through)到下一个 case。
严格比较( ===):与 if 语句不同,switch 语句只能做值的严格比较判断。
const num = '5'
switch (num) {
case 5: // '5' === 5
console.log('数字5') // 不会执行(类型不同)
break
case '5': // '5' === 5
console.log('字符串"5"') // 执行
break
}贯穿行为
贯穿行为(Fall-through):贯穿行为发生在:
- 当某个
case分支执行完毕后, - 没有遇到中断语句(如
break、return、throw), - 程序继续执行下一个
case分支的代码, - 无论下一个
case的条件是否匹配。
const day = 1
switch (day) {
case 1:
console.log('星期一')
// 这里没有 break!
case 2:
console.log('星期二')
break
case 3:
console.log('星期三')
break
}
// 输出:
// 星期一
// 星期二阻止贯穿行为:以下语句可以阻止贯穿行为:
break:立即退出switch语句。return:退出当前函数。throw:抛出异常。continue:在循环中跳过当前迭代(在循环内的switch中有效)。
最佳实践:
- 明确注释贯穿意图
- 使用代码风格规范:使用空行和注释区分
如何避免意外贯穿:
使用代码检查工具:配置 ESLint 规则:
js{ "rules": { "no-fallthrough": "error" } }防御性编程:在 default 分支抛出错误
jsfunction handleStatus(status) { switch (status) { case 'PENDING': return processPending() case 'ACTIVE': return processActive() case 'COMPLETED': return processCompleted() default: throw new Error(`未知状态: ${status}`) } }代码审查:在团队协作中:
- 特别注意没有
break的case - 审查贯穿逻辑是否合理
- 要求对贯穿行为添加明确注释
- 特别注意没有
循环语句
认识循环
在开发中我们经常需要做各种各样的循环操作:
- 比如把一个列表中的商品、歌曲、视频依次输出进行展示;
- 比如对一个列表进行累加计算;
- 比如运行相同的代码将数字 1 到 10 逐个输出;
循环(Loop):是编程中的基础控制结构,用于重复执行代码块,直到满足特定条件为止。
如果是对某一个列表进行循环操作,我们通常也会称之为 遍历(traversal)或者迭代(iteration);
循环分类:在 JavaScript 中支持三种循环方式:
- while
- do...while
- for
- for...in
- for...of
while
while 循环:用于在指定条件为真时重复执行代码块。它提供了一种在未知迭代次数的情况下执行重复操作的方法。
语法;
while (condition) {
// 循环体 - 条件为真时重复执行的代码
}while关键字:声明循环开始。- 条件表达式:每次迭代前评估,返回布尔值。
- 循环体:条件为真时执行的代码块。
- 迭代器:通常在循环体内修改条件变量。
执行流程:
- 条件评估:检查
condition是否为真。 - 执行循环体:如果条件为真,执行循环体内代码。
- 重复评估:执行完循环体后返回步骤 1。
- 退出循环:当条件为假时,跳过循环体继续执行后续代码。

示例:
简单计数器
jslet count = 1 while (count <= 5) { console.log(`计数: ${count}`) count++ // 关键:修改循环变量 } // 输出: // 计数: 1 // 计数: 2 // 计数: 3 // 计数: 4 // 计数: 5
对比 for:选择指南:
- 使用
while:当迭代次数未知(如等待用户输入、处理动态数据) - 使用
for:当迭代次数已知(如遍历数组、固定次数操作)
问题:死循环
问题:如果缺少迭代器更新,会造成条件一直成立(为 true),那么会产生死循环。
js// 危险:缺少迭代器更新 let i = 0 while (i < 5) { console.log(i) // 忘记 i++ → 无限循环! } // 解决方案:确保条件变量被修改解决方案:
- 关闭页面停止死循环。
- 确保条件变量被修改。
问题:作用域问题
问题:在循环外访问内部变量,会报
ReferenceErrorjs// 错误:在循环外访问内部变量 while (condition) { let temp = computeValue() } console.log(temp) // ReferenceError解决方案:在外部声明变量
js// 正确:在外部声明变量 let result while (condition) { result = computeValue() } console.log(result)
do...while
do...while 循环:是 JS 中的一种后测试循环结构,它首先执行循环体中的代码,然后再检查循环条件。与 while 循环不同,do...while 循环保证循环体至少执行一次,无论初始条件是否满足。
语法:
do {
// 循环体 - 至少执行一次
} while (condition)do关键字:标记循环体开始。- 循环体:包含要重复执行的代码。
while关键字:声明条件检查。- 条件表达式:决定是否继续循环。
- 分号:
while(condition)后必须加分号。
执行流程:
- 首次执行:无条件执行循环体。
- 条件评估:执行完循环体后检查条件。
- 重复执行:如果条件为真,返回步骤 1。
- 退出循环:如果条件为假,结束循环。

示例:
简单计数器
jslet count = 1 do { console.log(`计数: ${count}`) count++ } while (count <= 5) // 输出: // 计数: 1 // 计数: 2 // 计数: 3 // 计数: 4 // 计数: 5游戏菜单系统
jslet choice do { console.log('1. 开始游戏') console.log('2. 加载存档') console.log('3. 设置') console.log('4. 退出') choice = prompt('请选择:') switch (choice) { case '1': startGame() break case '2': loadGame() break case '3': openSettings() break case '4': console.log('再见!') break default: console.log('无效选择!') } } while (choice !== '4')
对比 while:推荐使用 while
| 特性 | do...while 循环 | while 循环 |
|---|---|---|
| 执行顺序 | 先执行后检查 | 先检查后执行 |
| 最少执行次数 | 至少 1 次 | 可能 0 次 |
| 适用场景 | 需要至少执行一次的操作 | 条件可能初始为假的情况 |
| 语法 | 需要结尾分号 | 不需要分号 |
| 可读性 | 条件在循环后,有时不够直观 | 条件在开头,结构清晰 |
问题:缺少分号
问题:在 do...while 后遗漏分号
js// 错误:在do...while后遗漏分号 do { // ... } while (condition) // 缺少分号 → 语法错误解决方案:始终添加分号
jsdo { // ... } while (condition)
问题:死循环:类似 while,如果缺少迭代器更新,会造成条件一直成立(为 true),那么会产生死循环。
问题:作用域问题:类似 while
for
for 循环:是 JS 中最常用的迭代控制结构,用于在已知迭代次数或需要遍历集合时重复执行代码块。它提供了一种简洁的方式将初始化、条件检查和迭代更新组合在单行语句中。
语法:
for (initialization; condition; finalExpression) {
// 循环体 - 重复执行的代码
}- 初始化 (initialization):循环开始前执行一次,通常用于声明计数器。
- 条件 (condition):每次迭代前检查,为真则执行循环体。
- 迭代更新 (finalExpression):每次迭代后执行,通常更新计数器。
- 循环体:重复执行的代码块。
执行流程:
- 初始化:执行初始化表达式(仅一次)。
- 条件检查:评估条件表达式。
- 如果
true→ 执行循环体。 - 如果
false→ 退出循环。
- 如果
- 执行循环体:运行循环体内的代码。
- 迭代更新:执行迭代表达式。
- 重复:返回步骤 2。
示例:
标准计数器(已知迭代次数)
jsfor (let i = 0; i < 5; i++) { console.log(`迭代次数: ${i}`) } // 输出: // 迭代次数: 0 // 迭代次数: 1 // 迭代次数: 2 // 迭代次数: 3 // 迭代次数: 4遍历数组(遍历集合)
jsconst fruits = ['苹果', '香蕉', '橙子'] for (let i = 0; i < fruits.length; i++) { console.log(`水果 ${i + 1}: ${fruits[i]}`) } // 输出: // 水果 1: 苹果 // 水果 2: 香蕉 // 水果 3: 橙子
递减循环:
for (let count = 10; count > 0; count--) {
console.log(count)
}
console.log('发射!')
// 输出: 10,9,8,7,6,5,4,3,2,1, 发射!带标签的 for 循环:用于嵌套循环中精确控制流程
// 标签
outerLoop: for (let i = 0; i < 3; i++) {
innerLoop: for (let j = 0; j < 3; j++) {
if (i === 1 && j === 1) {
break outerLoop // 跳出外层循环
}
console.log(`i=${i}, j=${j}`)
}
}
// 输出:
// i=0, j=0
// i=0, j=1
// i=0, j=2
// i=1, j=0性能优化:
缓存长度:保存集合长度到变量
js// 缓存长度 for (let i = 0, len = items.length; i < len; i++) { // 处理items[i] }减少 DOM 访问:保存获取的 DOM 元素到变量
js// 1. 减少DOM访问 const container = document.getElementById('container') const items = container.children倒序循环避免比较
js// 2. 倒序循环避免比较 for (let i = items.length - 1; i >= 0; i--) { // 处理items[i] }循环展开
js// 3. 循环展开 for (let i = 0; i < 100; i += 4) { process(i) process(i + 1) process(i + 2) process(i + 3) }
问题:死循环:类似 while
问题:作用域问题:类似 while
循环嵌套
循环嵌套(Nested Loops):是指:
- 一个循环(外层循环)内部包含另一个循环(内层循环),
- 内层循环在外层循环的每次迭代中都会完整执行,
- 嵌套层数理论上无限制,但通常不超过 3 层。
语法:
双重嵌套(最常见)
jsfor (let i = 0; i < 3; i++) { // 外层循环 console.log(`外层循环 i=${i}`)for (let j = 0; j < 2; j++) { // 内层循环 console.log(` 内层循环 j=${j}`) } } 混合类型嵌套
jslet row = 1 while (row <= 3) { // while 外层循环 console.log(`第${row}行:`)for (let col = 1; col <= 2; col++) { // for 内层循环 console.log(` 列${col}`) } row++ }
时间复杂度:嵌套层数增加会指数级增长执行时间。
- 单层循环:
O(n) - 双层嵌套:
O(n × m) - 三层嵌套:
O(n × m × p)
示例:
遍历二维数组
jsconst matrix = [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ] // 遍历二维数组 for (let i = 0; i < matrix.length; i++) { for (let j = 0; j < matrix[i].length; j++) { console.log(`matrix[${i}][${j}] = ${matrix[i][j]}`) } }
问题:性能问题
- 问题:嵌套循环导致指数级时间复杂度增长
- 解决方案:
- 优化算法复杂度(如使用哈希表替代嵌套搜索)
- 减少循环层数
- 使用并行处理(Web Workers)
循环控制
循环的跳转(控制):
- 在执行循环过程中, 遇到某一个条件时, 我们可能想要做一些事情;
- 比如循环体不再执行(即使没有执行完), 跳出循环;
- 比如本次循环体不再执行, 执行下一次的循环体;
循环的跳转控制
break: 直接跳出循环, 循环结束
- break 某一条件满足时,退出循环,不再执行后续重复的代码
continue: 跳过本次循环次, 执行下一次循环体
- continue 指令是 break 的“轻量版”。
- continue 某一条件满足时,不执行后续重复的代码
练习:猜数字游戏:游戏规则:
- 电脑随机生成一个 0~99 之间的数字;
- 玩家有 7 次猜测的机会;
- 玩家猜测一个数字, 输入到程序中;
- 电脑根据输入的数字打印: 猜大了/猜小了/猜对了的情况;
- 猜对了, 那么游戏结束,显示恭喜您;
- 7 次机会用完打印: 您的次数用完了, 您失败了;